package ua.com.stormlabs.tiny.licenseserver.controllers;

import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import ua.com.stormlabs.tiny.licenseserver.beans.License;
import ua.com.stormlabs.tiny.licenseserver.beans.Pager;
import ua.com.stormlabs.tiny.licenseserver.beans.SortingColumnDesc;
import ua.com.stormlabs.tiny.licenseserver.beans.User;
import ua.com.stormlabs.tiny.licenseserver.service.LicensesService;
import ua.com.stormlabs.tiny.licenseserver.service.UsersService;
import ua.com.stormlabs.tiny.licenseserver.storage.*;
import ua.com.stormlabs.tiny.licenseserver.utils.ArgumentGuard;
import ua.com.stormlabs.tiny.licenseserver.utils.UsersIdComparator;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.*;

/**
 * @author Maxim Bondarenko
 * @version 1.0 7/11/11
 */

@Controller
public class UsersController {

    private static final Logger log = LoggerFactory.getLogger(UsersController.class);

    private static final ResourceBundle bundle = ResourceBundle.getBundle("application");

    private static final int CODE_LENGTH = 20;
    private static final int PAGE_SIZE = 2;

    @Autowired
    UsersService usersService;

    @Autowired
    LicensesService licensesService;

    @Secured({"ROLE_ADMIN", "ROLE_STAFF"})
    @SuppressWarnings("unchecked")
    @RequestMapping(value = "users/getData")
    @ResponseBody
    public Map findUsers(HttpServletRequest request) {
        Map<String, Object> parameters = request.getParameterMap();
        Pageable fetchLimits = DataTableUtils.parseFetchLimits(parameters);

        if (fetchLimits.getSort().getOrderFor("licenseName") != null) {
            List<Sort.Order> orders = new LinkedList<>();
            Iterator<Sort.Order> orderIterator = fetchLimits.getSort().iterator();
            while (orderIterator.hasNext()) {
                Sort.Order order = orderIterator.next();
                if (order.getProperty().equals("licenseName")) {
                    Sort.Order newOrder = new Sort.Order(order.getDirection(), "license.licenseName");
                    orders.add(newOrder);
                } else {
                    orders.add(order);
                }
            }
            fetchLimits = new PageRequest(fetchLimits.getPageNumber(), fetchLimits.getPageSize(), new Sort(orders));
        }
        log.debug("Getting users, limits: " + fetchLimits);
        return DataTableUtils.wrapResult(usersService.listUsers(fetchLimits), parameters);
    }

    @Secured({"ROLE_ADMIN", "ROLE_STAFF"})
    @RequestMapping(value = "users/view", method = RequestMethod.GET)
    public String view(Model model, @RequestParam(defaultValue = "1") Integer pageNumber) {
        log.debug("Java version: " + System.getProperty("java.version"));
        log.debug("Getting users");

        RequestResult<User> usersRes = usersService.listUsers(pageNumber, PAGE_SIZE);
        log.info("Found " + usersRes.getTotalCount() + " users");
        Collections.sort(usersRes.getResult(), new UsersIdComparator());
        model.addAttribute("users", usersRes.getResult());
        model.addAttribute("pager", new Pager(pageNumber, usersRes.getTotalCount(), PAGE_SIZE));
        return "users/view";
    }

    @Secured({"ROLE_ADMIN", "ROLE_STAFF"})
    @RequestMapping(value = "users/resetMac", method = RequestMethod.GET)
    public String resetUserMac(@RequestParam Integer id,
                               @RequestParam(defaultValue = "1") Integer pageNumber) {
        log.info("Resetting mac for user: " + id);

        try {
            usersService.resetUserMac(id);
        } catch (UserNotFoundException e) {
            log.error("Exception: " + e, e);
        }

        return "redirect:/users/view?pageNumber=" + pageNumber;
    }

    @Secured({"ROLE_ADMIN", "ROLE_STAFF"})
    @RequestMapping(value = "users/deleteUser", method = RequestMethod.GET)
    public String deleteUser(@RequestParam Integer id,
                             @RequestParam(defaultValue = "1") Integer pageNumber) {
        log.info("Deleting user with id: " + id);

        try {
            usersService.deleteUserById(id);
        } catch (UserNotFoundException e) {
            log.error("Exception: " + e, e);
        }

        return "redirect:/users/view?pageNumber=" + pageNumber;
    }

    @Secured({"ROLE_ADMIN", "ROLE_STAFF"})
    @RequestMapping(value = "users/add", method = RequestMethod.GET)
    public String addUser(Model model) {
        log.info("Adding new user");

        User user = new User();
        user.setId(-1);
        user.setUserName("Mr.Smith");
        user.setCreateDate(new Date());
        Date expireDate = new Date(new Date().getTime() + Long.parseLong(bundle.getString("license.expireTerm.sec")) * 1000);
        user.setExpireDate(expireDate);
        user.setLicenseCode(RandomStringUtils.randomAlphanumeric(CODE_LENGTH));
        user.setMac(null);
        user.setMacChecking(true);

        model.addAttribute("user", user);
        model.addAttribute("licensesMap", getAvailableLicenses());

        return "users/edit";
    }

    @Secured({"ROLE_ADMIN", "ROLE_STAFF"})
    @RequestMapping(value = "users/add", method = RequestMethod.POST)
    public String insert(@Valid User user, Model model) {
        user.setMac(null);
        log.info("Inserting user: " + user);
        try {
            usersService.createUser(user);
        } catch (DuplicateUserException e) {
            log.warn("Duplicate name");
            user.setMac(null); //required for UI
            model.addAttribute("user", user);
            model.addAttribute("licensesMap", getAvailableLicenses());
            model.addAttribute("error", "name");
            return "users/edit";
        } catch (LicenseNotFoundException e) {
            log.error("Exception: " + e, e);
        }
        log.info("Event inserted: " + user);
        return "redirect:/users/view";
    }

    private Map<Integer, String> getAvailableLicenses() {
        List<License> licenses = licensesService.listAllLicenses();
        Map<Integer, String> licensesMap = new HashMap<Integer, String>();
        for (License license : licenses) {
            licensesMap.put(license.getId(), license.getLicenseName());
        }

        return licensesMap;
    }

    @Secured({"ROLE_ADMIN", "ROLE_STAFF"})
    @RequestMapping(value = "users/edit/{id}", method = RequestMethod.GET)
    public String edit(@PathVariable Integer id, Model model) {
        log.info("Editing user: " + id);

        User user = null;
        try {
            user = usersService.getUserById(id);
        } catch (UserNotFoundException e) {
            log.error("Exception: " + e, e);
        }

        model.addAttribute("user", user);
        model.addAttribute("licensesMap", getAvailableLicenses());
        return "users/edit";
    }

    @Secured({"ROLE_ADMIN", "ROLE_STAFF"})
    @RequestMapping(value = "users/update", method = RequestMethod.POST)
    public String update(@Valid User user, Model model) {
        log.debug("Updating user: " + user);
        if (StringUtils.isBlank(user.getMac())) {
            user.setMac(null);
        }
        try {
            usersService.updateUser(user);
        } catch (UserNotFoundException e) {
            log.error("Exception: " + e, e);
        } catch (LicenseNotFoundException e) {
            log.error("Exception: " + e, e);
        }

        log.info("User updated: " + user);
        return "redirect:/users/view";
    }

    @Secured({"ROLE_ADMIN", "ROLE_STAFF"})
    @RequestMapping(value = "users/isUnique", method = RequestMethod.GET)
    @ResponseBody
    public Boolean isUserNameUnique(@RequestParam String userName, @RequestParam int userId) throws UserNotFoundException {
        ArgumentGuard.checkNotNull(userName, "userName");
        ArgumentGuard.checkNotBlank(userName, "userName");

        log.debug("Checking user name: [" + userName + "], user id: [" + userId + "]");

        if (userId == -1) {
            try {
                usersService.getUserByName(userName);
                log.debug("User with name [" + userName + "] already exists");
                return false;
            } catch (UserNotFoundException e) {
                log.debug("User with name [" + userName + "] unique and can be created");
                return true;
            }
        } else {
            User userById = usersService.getUserById(userId);
            try {
                User userByName = usersService.getUserByName(userName);
                if (userById.getUserName().equals(userByName.getUserName())) {
                    log.debug("User with name [" + userName + "] duplicates current user name and can be modified");
                    return true;
                } else {
                    log.debug("User with name [" + userName + "] already exists");
                    return false;
                }
            } catch (UserNotFoundException e) {
                log.debug("User with name [" + userName + "] unique and can be created");
                return true;
            }
        }
    }
}
